---
title: "Spring Security中的OAuth2功能"
categories:
- java
- spring-security
tags:
---

<div id="content">
<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#org21426ef">调用堆栈：</a>
<ul>
<li><a href="#orga0923f4">Authentication :: 验证用户是否登录</a></li>
<li><a href="#orgd0adf7a">Authorization :: 验证登录的用户是否有权限访问资源</a></li>
</ul>
</li>
<li><a href="#org8349583">Classes</a>
<ul>
<li><a href="#org78a2809">TokenStore</a></li>
<li><a href="#org3ec2a29">ClientDetailsService</a></li>
<li><a href="#org1fc1e79">AuthorizationServerTokenService</a></li>
<li><a href="#org41f72b5">ResourceServerTokenServices 资源服务器读取Token的接口</a>
<ul>
<li><a href="#orgb23d827">实现类：</a></li>
</ul>
</li>
<li><a href="#orgd267aac">TokenEnhancer</a></li>
<li><a href="#org5254a4c">AccessTokenConverter</a></li>
</ul>
</li>
<li><a href="#orge2942de">OAuth2 Server Config 授权服务器的配置</a>
<ul>
<li><a href="#org1a8a571">void configure(AuthorizationServerSecurityConfigurer)</a></li>
<li><a href="#org5bc9fad">void configure(AuthorizationServerEndpointsConfigurer)</a></li>
<li><a href="#org2b9782f">void configure(ClientDetailsServiceConfigurer)</a></li>
</ul>
</li>
<li><a href="#org739daad">OAuth2 Resource Config 资源服务器配置</a></li>
<li><a href="#org42e8163">Spring OAuth2 and JWT</a>
<ul>
<li><a href="#org4e618db">Jwt OAuth2 Authorization Server Config</a></li>
<li><a href="#orgf8f8687">Jwt OAuth2 Resource Server Config</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="outline-2" id="outline-container-org21426ef">
<h2 id="org21426ef">调用堆栈：</h2>
<div class="outline-text-2" id="text-org21426ef">
</div>
<div class="outline-3" id="outline-container-orga0923f4">
<h3 id="orga0923f4">Authentication :: 验证用户是否登录</h3>
<div class="outline-text-3" id="text-orga0923f4">
<pre class="example">
# 调用ResourceServerTokenServices#loadAuthentication（实现类不一定是DefaultTokenServices）
loadAuthentication:229, DefaultTokenServices (org.springframework.security.oauth2.provider.token)
authenticate:83, OAuth2AuthenticationManager (org.springframework.security.oauth2.provider.authentication)
doFilter:150, OAuth2AuthenticationProcessingFilter (org.springframework.security.oauth2.provider.authentication)
</pre>
</div>
</div>
<div class="outline-3" id="outline-container-orgd0adf7a">
<h3 id="orgd0adf7a">Authorization :: 验证登录的用户是否有权限访问资源</h3>
<div class="outline-text-3" id="text-orgd0adf7a">
<p>
AbstractSecurityInterceptor 
this.accessDecisionManager.decide(authenticated, object, attributes);
</p>
</div>
</div>
</div>
<div class="outline-2" id="outline-container-org8349583">
<h2 id="org8349583">Classes</h2>
<div class="outline-text-2" id="text-org8349583">
</div>
<div class="outline-3" id="outline-container-org78a2809">
<h3 id="org78a2809">TokenStore</h3>
<div class="outline-text-3" id="text-org78a2809">
<p>
这个接口中的方式用于对OAuth2 Token的保存与读取，例如：
</p>
<ul class="org-ul">
<li>void storeAccessToken(OAuth2AccessToken, OAuth2Authentication) 保存</li>
<li>void removeAccessToken(OAuth2AccessToken) 从数据源中移除AccessToken</li>
</ul>
</div>
</div>
<div class="outline-3" id="outline-container-org3ec2a29">
<h3 id="org3ec2a29">ClientDetailsService</h3>
<div class="outline-text-3" id="text-org3ec2a29">
<p>
这个接口中只有一个方法，就是根据ClientID加载Client的信息。
这里的Client在OAuth2中就是客户端程序，面不是用户终端。ClientDetails包括ClientSecret,Scope等。
</p>
</div>
</div>
<div class="outline-3" id="outline-container-org1fc1e79">
<h3 id="org1fc1e79">AuthorizationServerTokenService</h3>
<div class="outline-text-3" id="text-org1fc1e79">
<p>
这个就是OAuth2中进行AccessToken创建的接口。包含三个方法，
</p>
<ul class="org-ul">
<li>OAuth2AccessToken createAccessToken(OAuth2Authentication) 根据传递的凭据，创建AccessToken</li>
<li>OAuth2AccessToken refreshAccessToken(refreshToken, TokenRequest) OAuth2中的refreshToken机制，作用refreshToken交换新的AccessToken时被调用</li>
<li>OAuth2AccessToken getAccessToken(OAuth2Authentication) 如果Authentication对应的AccessToken存在则返回</li>
</ul>
</div>
</div>
<div class="outline-3" id="outline-container-org41f72b5">
<h3 id="org41f72b5">ResourceServerTokenServices 资源服务器读取Token的接口</h3>
<div class="outline-text-3" id="text-org41f72b5">
<p>
包含两个方法
</p>
<ol class="org-ol">
<li>loadAuthentication 从token字符串读取Authentication</li>
<li>readAccessToken 从token字符串读取Access</li>
</ol>
</div>
<div class="outline-4" id="outline-container-orgb23d827">
<h4 id="orgb23d827">实现类：</h4>
<div class="outline-text-4" id="text-orgb23d827">
<ul class="org-ul">
<li>DefaultTokenServices: 需要一个TokenStore，实际是从TokenStore读取。这个类也是AuthorizationServerTokenServices的默认实现</li>
<li>UserInfoTokenServices: 从远端服务器（授权服务器）读取Token。</li>
<li>RemoteTokenServices: 也是通过远端服务器读取Token，与UserInfoTokenServices类似，内部发出的HTTP请求不同</li>
</ul>
<p>
ResourceServerTokenServicesConfiguration类中包含有自动配置这些类的逻辑：
</p>
<ul class="org-ul">
<li>UserInfoTokenService: NotTokenInfoCondition</li>
</ul>
<p>
Conditions:
</p>
<ul class="org-ul">
<li>TokenInfoCondition: Match if (or)
<ul class="org-ul">
<li>security.oauth2.resource.token-info-uri 和 security.oauth2.resource.user-info-uri都没有配置时</li>
<li>security.oauth2.resource.token-info-uri有值且security.oauth2.resource.prefer-token-info=true时</li>
</ul></li>
<li>NotTokenInfoCondition: !TokenInfoCondition</li>
</ul>
</div>
</div>
</div>
<div class="outline-3" id="outline-container-orgd267aac">
<h3 id="orgd267aac">TokenEnhancer</h3>
<div class="outline-text-3" id="text-orgd267aac">
<p>
在AccessToken被AuthorizationServerTokenService存储之前，对AceessToken进行修改。
这个接口只包含一个方法，传递OAuth2AccessToken &amp; OAuth2Authentication，实现方法进行修改，然后将修改后的AccessToken返回。
</p>
</div>
</div>
<div class="outline-3" id="outline-container-org5254a4c">
<h3 id="org5254a4c">AccessTokenConverter</h3>
<div class="outline-text-3" id="text-org5254a4c">
<p>
网络上传递的token是以字符串的形式，程序内当然是用对象表示方便。这个接口中提供了string, map, AccessToken对象之间的转换方法。
</p>
</div>
</div>
</div>
<div class="outline-2" id="outline-container-orge2942de">
<h2 id="orge2942de">OAuth2 Server Config 授权服务器的配置</h2>
<div class="outline-text-2" id="text-orge2942de">
<p>
项目中引用了Spring-Security-OAuth2之后，通过继承 {% raw %} AuthorizationServerConfigurerAdapter {% endraw %} ，来自定义配置OAuth2 Server。
</p>
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; text-decoration: underline;">@Configuration</span>
<span style="font-weight: bold; text-decoration: underline;">@EnableAuthorizationServer</span>
<span style="font-weight: bold;">public</span> <span style="font-weight: bold;">class</span> <span style="font-weight: bold; text-decoration: underline;">OAuth2ServerConfig</span> <span style="font-weight: bold;">extends</span> <span style="font-weight: bold; text-decoration: underline;">AuthorizationServerConfigurerAdapter</span> {

    <span style="font-style: italic;">/**</span>
<span style="font-style: italic;">     * 对endpoint的自定义配置</span>
<span style="font-style: italic;">     */</span>
    <span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold;">configure</span>(<span style="font-weight: bold; text-decoration: underline;">AuthorizationServerEndpointsConfigurer</span> <span style="font-weight: bold; font-style: italic;">endpoints</span>);




}
</pre>
</div>
<p>
注解 {% raw %} EnableAuthorizationServer {% endraw %} 用于开启OAuth2Server的功能。开启后，应用程序新增了几个EndPoint：例如
</p>
<ul class="org-ul">
<li>/oauth/authorize</li>
<li>/oauth/token</li>
<li>/oauth/check_token</li>
<li>/oauth/confirm_access</li>
<li>/oauth/error</li>
</ul>
<p>
对应着OAuth2各流程。
这个类中有几个方法能够被重写：
</p>
</div>
<div class="outline-3" id="outline-container-org1a8a571">
<h3 id="org1a8a571">void configure(AuthorizationServerSecurityConfigurer)</h3>
</div>
<div class="outline-3" id="outline-container-org5bc9fad">
<h3 id="org5bc9fad">void configure(AuthorizationServerEndpointsConfigurer)</h3>
<div class="outline-text-3" id="text-org5bc9fad">
<p>
在这个方法中，能够自定义 tokenStore，accessTokenConverter, tokenEnhancer, userDetailsService, authenticationManager等。
如果不重写这个方法，则框架创建默认的配置（AuthorizationServerEndpointsConfigurer.createDefault*），一般默认配置足够，配置Jwt支持的时候，会要重写这个方法。
</p>
</div>
</div>
<div class="outline-3" id="outline-container-org2b9782f">
<h3 id="org2b9782f">void configure(ClientDetailsServiceConfigurer)</h3>
<div class="outline-text-3" id="text-org2b9782f">
<p>
重写这个方法，自定义配置 ClientDetailsService。例如：
</p>
<ul class="org-ul">
<li>使用 方法参数给的ClientDetailsServiceConfigurer对象提供的流式API，将clients的信息保存在内存中</li>
<li>自己继承 ClientDetailsService，实现从自己的数据源中读取client的信息。</li>
</ul>
</div>
</div>
</div>
<div class="outline-2" id="outline-container-org739daad">
<h2 id="org739daad">OAuth2 Resource Config 资源服务器配置</h2>
<div class="outline-text-2" id="text-org739daad">
<p>
通过 {% raw %} EnableResourceServer {% endraw %} 开启ResourceServer功能。通过继承 {% raw %} ResourceServerConfigurerAdapter {% endraw %} 自定义 Resource Server的配置。
</p>
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; text-decoration: underline;">@EnableResourceServer</span>
<span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold; font-style: italic;">OAuth2ResourceServerConfig</span> <span style="font-weight: bold;">extends</span> <span style="font-weight: bold; text-decoration: underline;">ResourceServerConfigurerAdapter</span> {

    <span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold;">configure</span>(<span style="font-weight: bold; text-decoration: underline;">ResourceServerSecurityConfigurer</span>);


}
</pre>
</div>
</div>
</div>
<div class="outline-2" id="outline-container-org42e8163">
<h2 id="org42e8163">Spring OAuth2 and JWT</h2>
<div class="outline-text-2" id="text-org42e8163">
<p>
Jwt的AccessToken不需要存储到数据源中，所有信息都包含在token字符串里面了。但是它需要反序列化，并且验证签名。
Spring提供了 {% raw %} JwtAccessTokenConverter {% endraw %} {% raw %} JwtTokenStore {% endraw %}
</p>
<div class="figure">
<p><img alt="jwt-classes.png" src=".ouath/jwt-classes.png"/>
</p>
</div>
<p>
JwtAccessTokenConverter内部有一个AccessTokenConverter（默认是DefaultAccessTokenConverter），实现AccessTokenConverter接口都是通过调用内部的那个对象。
实现TokenEnhancer接口是做一些jwt相关的操作。
它还包括反序列化、验证命名的方法，这些方法被JwtTokenStore使用。
</p>
<p>
JwtTokenStore内部Store的方法为空，因为Jwt不需要存储到数据源中。Read的方法，通过内部的JwtAccessTokenConverter对象，进行字符串token与AccessToken对象之间的转化。
</p>
</div>
<div class="outline-3" id="outline-container-org4e618db">
<h3 id="org4e618db">Jwt OAuth2 Authorization Server Config</h3>
<div class="outline-text-3" id="text-org4e618db">
<p>
配置Oauth2认证服务器支持jwt，入口仍然是 {% raw %} AuthorizationServerConfigurerAdapter {% endraw %} ，需要将 JwtTokenStore 和 JwtTokenConverter 替换掉默认的配置。
</p>
<div class="org-src-container">
<pre class="src src-java">
<span style="font-weight: bold; text-decoration: underline;">@Configuration</span>
<span style="font-weight: bold; text-decoration: underline;">@EnalbeAuthorizationServer</span>
<span style="font-weight: bold;">public</span> <span style="font-weight: bold;">class</span> <span style="font-weight: bold; text-decoration: underline;">OAuthServerConfig</span> <span style="font-weight: bold;">extends</span> <span style="font-weight: bold; text-decoration: underline;">AuthorizationServerConfigurerAdapter</span> {

    <span style="font-weight: bold; text-decoration: underline;">@Bean</span>
    <span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">TokenStore</span> <span style="font-weight: bold;">tokenStore</span>() {
        <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">create JwtTokenStore()</span>
    }

    <span style="font-weight: bold; text-decoration: underline;">@Bean</span>
    <span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">JwtAccessTokenConverter</span> <span style="font-weight: bold;">accessTokenConverter</span>() {
        <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">create object JwtAccessTokenConverter</span>
        <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">JwtAccessTokenConverter 既是TokenConverter，也是TokenEnhancer，声明为Bean，而且是单例模式的。</span>
        <span style="font-weight: bold; text-decoration: underline;">var</span> <span style="font-weight: bold; font-style: italic;">retn</span> = <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">JwtAccessTokenConverter</span>();
        <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">配置 JwtAccessTokenConverter</span>
        <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">retn.setKeyPair() 这个方法设置认证签名时作用的密钥</span>
        <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">不要忘记调用JwtAccessTokenConverter.afterPropertiesSet()方法，这里因为声明成了bean，spring框架会调用这个方法。</span>
        <span style="font-weight: bold;">return</span> retn;
    }

    <span style="font-weight: bold; text-decoration: underline;">@Override</span>
    <span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold;">configure</span>(<span style="font-weight: bold; text-decoration: underline;">AuthorizationServerEndpointsConfigurer</span> <span style="font-weight: bold; font-style: italic;">endpoints</span>) {
        endpoints.tokenStore(tokenStore()) <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">使用上面创建的TokenStore bean</span>
            .accessTokenConverter(accessTokenConverter()) <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">无论是配置accessTokenConverter还是tokenEnhancer，都使用的那一个bean</span>
            .tokenEnhancer(accessTokenConverter())
            <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">框架提供了另外一个名为TokenEnhancerChain人类，也实现了TokenEnhancer，它里边包含了TokenEnhancer的列表，调用它的enhance方法时，列表中所有的enhancer方法都被调用</span>
            <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">当希望自定义生成的AccessToken时，构造这个类，然后把默认的TokenEnhancer和自己的TokenEnhancer实现传递给它</span>
             ...
     }
 }
</pre>
</div>
</div>
</div>
<div class="outline-3" id="outline-container-orgf8f8687">
<h3 id="orgf8f8687">Jwt OAuth2 Resource Server Config</h3>
<div class="outline-text-3" id="text-orgf8f8687">
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; text-decoration: underline;">@Configuration</span>
<span style="font-weight: bold; text-decoration: underline;">@EnableResourceServer</span>
<span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">ResourceServerConfig</span> <span style="font-weight: bold;">extends</span> <span style="font-weight: bold; text-decoration: underline;">ResourceServerConfigurerAdapter</span> {

    <span style="font-weight: bold; text-decoration: underline;">DefaultTokenServices</span> <span style="font-weight: bold;">resourceServerTokenServices</span>() {
        <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">创建JwtAccessTokenConverter</span>
        <span style="font-weight: bold; text-decoration: underline;">var</span> <span style="font-weight: bold; font-style: italic;">converter</span> = <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">JwtAccessTokenConverter</span>();
        converter.setVerifierKey(key); <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">设置验证token是使用的密钥。</span>
        converter.afterPropertiesSet();
        <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">创建TokenStore</span>
        <span style="font-weight: bold; text-decoration: underline;">var</span> <span style="font-weight: bold; font-style: italic;">tokenStore</span> = <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">JwtTokenStore</span>(conveter);
        <span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">创建TokenService</span>
        <span style="font-weight: bold; text-decoration: underline;">var</span> <span style="font-weight: bold; font-style: italic;">retn</span> = <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">DefaultTokenServices</span>();
        tokenService.setTokenStore(tokenStore);
        <span style="font-weight: bold;">return</span> retn;
    }

    <span style="font-weight: bold; text-decoration: underline;">@Override</span>
        <span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">void</span> <span style="font-weight: bold;">configurer</span>(<span style="font-weight: bold; text-decoration: underline;">ResourceServerSecurityConfigurer</span> <span style="font-weight: bold; font-style: italic;">resources</span>) {
        <span style="font-weight: bold; text-decoration: underline;">var</span> <span style="font-weight: bold; font-style: italic;">tokenService</span> = resourceServerTokenServices();
        resources.tokenSerivces(tokenService);
    }


</pre>
</div>
</div>
</div>
</div>
</div>
<div class="status" id="postamble">
<p class="author">Author: gdme1320</p>
<p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p>
</div>
